# coding=utf8
from typing import Union, Optional, TypeVar

from pyjvm.classfile.clstype import *
from pyjvm.classfile.reader import ClassBytesReader


class ConstantInfoTag:
    Utf8 = 1  # CONSTANT_Utf8
    Integer = 3  # CONSTANT_Integer
    Float = 4  # CONSTANT_Float
    Long = 5  # CONSTANT_Long
    Double = 6  # CONSTANT_Double
    Class = 7  # CONSTANT_Class
    String = 8  # CONSTANT_String
    FieldRef = 9  # CONSTANT_Fieldref
    MethodRef = 10  # CONSTANT_Methodref
    InterfaceMethodRef = 11  # CONSTANT_InterfaceMethodref
    NameAndType = 12  # CONSTANT_NameAndType
    MethodHandle = 15  # CONSTANT_MethodHandle
    MethodType = 16  # CONSTANT_MethodType
    InvokeDynamic = 18  # CONSTANT_InvokeDynamic

    WideTagSet = {Long, Double}
    ConstValSet = {Integer, Float, Long, Double}


class ConstantInfo:
    pass


T_ConstInfo = TypeVar('T_ConstInfo')


class ConstantPool(list):

    def __init__(self):
        list.__init__(self, [None])

    def read_utf8(self, reader: ClassBytesReader) -> str:
        return self.utf8_at_u2(reader.u2())

    def read_info(self, reader: ClassBytesReader) -> T_ConstInfo:
        return self.info_at_u2(reader.u2())

    def info_at_u2(self, u2idx: u2) -> T_ConstInfo:
        return self[uint16(u2idx)]

    def utf8_at(self, index: int) -> str:
        return self[index].text

    def utf8_at_u2(self, u2idx: u2) -> str:
        return self.utf8_at(uint16(u2idx))

    def value_at(self, index: int) -> Union[int, float, str]:
        info = self[index]
        if isinstance(info, ConstantUtf8Info):
            return info.text
        elif isinstance(info, ConstantStringInfo):
            return info.string
        return info.value

    def value_at_u2(self, u2idx: u2):
        return self.value_at(uint16(u2idx))

    def read_value(self, reader: ClassBytesReader) -> Union[int, float, str]:
        return self.value_at_u2(reader.u2())


class ConstantClassInfo(ConstantInfo):
    tag = ConstantInfoTag.Class

    def __init__(self):
        self.name_index: u2 = None
        self.__name: Optional[str] = None
        self.__cp: Optional[ConstantPool] = None

    def parse(self, reader: ClassBytesReader, cp: ConstantPool):
        self.name_index = reader.u2()
        self.__cp = cp

    @property
    def class_name(self):
        if self.__name is None:
            self.__name = self.__cp.utf8_at_u2(self.name_index)
        return self.__name

    def __str__(self):
        return '%s[class_name:%s]' % (self.__class__.__name__, self.class_name)


class ConstantNameAndTypeInfo(ConstantInfo):
    tag = ConstantInfoTag.NameAndType

    def __init__(self):
        self.name_index: u2 = None
        self.descriptor_index: u2 = None

        self.__cp: Optional[ConstantPool] = None
        self.__name: Optional[str] = None
        self.__descriptor: Optional[str] = None

    def parse(self, reader: ClassBytesReader, cp: ConstantPool):
        self.name_index = reader.u2()
        self.descriptor_index = reader.u2()
        self.__cp = cp

    def __pre_name_and_descriptor(self):
        if self.__name is None:
            self.__name = self.__cp.utf8_at_u2(self.name_index)
            self.__descriptor = self.__cp.utf8_at_u2(self.descriptor_index)

    @property
    def name(self):
        self.__pre_name_and_descriptor()
        return self.__name

    @property
    def descriptor(self):
        self.__pre_name_and_descriptor()
        return self.__descriptor

    def __str__(self):
        return '%s[name:%s, descriptor: %s]' % (self.__class__.__name__, self.name, self.descriptor)


class BaseConstantRefInfo(ConstantInfo):

    def __init__(self):
        self.class_index: u2 = None
        self.name_and_type_index: u2 = None

        self.__cp: Optional[ConstantPool] = None
        self.__class_name: Optional[str] = None
        self.__name: Optional[str] = None
        self.__descriptor: Optional[str] = None

    def parse(self, reader: ClassBytesReader, cp: ConstantPool):
        self.class_index = reader.u2()
        self.name_and_type_index = reader.u2()
        self.__cp = cp

    def __prepare(self):
        if self.__class_name is None:
            class_info: ConstantClassInfo = self.__cp.info_at_u2(self.class_index)
            self.__class_name = class_info.class_name
            name_and_type_info: ConstantNameAndTypeInfo = self.__cp.info_at_u2(self.name_and_type_index)
            self.__name = name_and_type_info.name
            self.__descriptor = name_and_type_info.descriptor

    @property
    def class_name(self):
        self.__prepare()
        return self.__class_name

    @property
    def name(self):
        self.__prepare()
        return self.__name

    @property
    def descriptor(self):
        self.__prepare()
        return self.__descriptor

    def __str__(self):
        return '%s[class_name:%s ' \
               'name:%s, descriptor: %s]' % (self.__class__.__name__,
                                             self.class_name,
                                             self.name, self.descriptor)


class ConstantFieldRefInfo(BaseConstantRefInfo):
    tag = ConstantInfoTag.FieldRef


class ConstantMethodRefInfo(BaseConstantRefInfo):
    tag = ConstantInfoTag.MethodRef


class ConstantInterfaceMethodRefInfo(BaseConstantRefInfo):
    tag = ConstantInfoTag.InterfaceMethodRef


class ConstantStringInfo(ConstantInfo):
    tag = ConstantInfoTag.String

    def __init__(self):
        self.string_index: u2 = None
        self.__str: Optional[str] = None
        self.__cp: Optional[ConstantPool] = None

    def parse(self, reader: ClassBytesReader, cp: ConstantPool):
        self.string_index = reader.u2()
        self.__cp = cp

    @property
    def string(self):
        if self.__str is None:
            idx = uint16(self.string_index)
            self.__str = self.__cp[idx].text
        return self.__str

    def __str__(self):
        return '%s[string:%s]' % (self.__class__.__name__, self.string)


class ConstantIntegerInfo(ConstantInfo):
    tag = ConstantInfoTag.Integer

    def __init__(self):
        self.value: Optional[int] = None

    def parse(self, reader: ClassBytesReader):
        self.value = reader.int32()

    def __str__(self):
        return '%s[value:%s]' % (self.__class__.__name__, self.value)


class ConstantFloatInfo(ConstantInfo):
    tag = ConstantInfoTag.Float

    def __init__(self):
        self.value: Optional[float] = None

    def parse(self, reader: ClassBytesReader):
        self.value = reader.float()

    def __str__(self):
        return '%s[value:%s]' % (self.__class__.__name__, self.value)


class ConstantLongInfo(ConstantInfo):
    tag = ConstantInfoTag.Long

    def __init__(self):
        self.value: Optional[int] = None

    def parse(self, reader: ClassBytesReader):
        self.value = reader.long()

    def __str__(self):
        return '%s[value:%s]' % (self.__class__.__name__, self.value)


class ConstantDoubleInfo(ConstantInfo):
    tag = ConstantInfoTag.Double

    def __init__(self):
        self.value: Optional[float] = None

    def parse(self, reader: ClassBytesReader):
        self.value = reader.double()

    def __str__(self):
        return '%s[value:%s]' % (self.__class__.__name__, self.value)


class ConstantMethodHandleInfo(ConstantInfo):
    tag = ConstantInfoTag.MethodHandle

    def __init__(self):
        self.reference_kind: u1 = None
        self.reference_index: u2 = None

    def parse(self, reader: ClassBytesReader):
        self.reference_kind = reader.u1()
        self.reference_index = reader.u2()


class ConstantUtf8Info(ConstantInfo):
    tag = ConstantInfoTag.Utf8

    def __init__(self):
        self.text: Optional[str] = None

    def parse(self, reader: ClassBytesReader):
        self.text = reader.utf8()

    def __str__(self):
        return '%s[text:%s]' % (self.__class__.__name__, self.text)


class ConstantMethodTypeInfo(ConstantInfo):
    tag = ConstantInfoTag.MethodType

    def __init__(self):
        self.descriptor_index: u2 = None

        self.__cp: Optional[ConstantPool] = None
        self.__descriptor: Optional[str] = None

    def parse(self, reader: ClassBytesReader, cp: ConstantPool):
        self.descriptor_index = reader.u2()
        self.__cp = cp

    @property
    def descriptor(self):
        if self.__descriptor is None:
            self.__descriptor = self.__cp.utf8_at_u2(self.descriptor_index)
        return self.__descriptor

    def __str__(self):
        return '%s[descriptor:%s]' % (self.__class__.__name__, self.descriptor)


class ConstantInvokeDynamicInfo(ConstantInfo):
    tag = ConstantInfoTag.InvokeDynamic

    def __init__(self):
        self.bootstrap_method_attr_index: u2 = None
        self.name_and_type_index: u2 = None

    def parse(self, reader: ClassBytesReader):
        self.bootstrap_method_attr_index = reader.u2()
        self.name_and_type_index = reader.u2()

    def __str__(self):
        return '%s[bootstrap_method_attr_index:%s]' % (self.__class__.__name__, self.bootstrap_method_attr_index)


CONSTANT_INFO_MAP = {
    ConstantInfoTag.Utf8: ConstantUtf8Info,
    ConstantInfoTag.Integer: ConstantIntegerInfo,
    ConstantInfoTag.Long: ConstantLongInfo,
    ConstantInfoTag.Float: ConstantFloatInfo,
    ConstantInfoTag.Double: ConstantDoubleInfo,
    ConstantInfoTag.FieldRef: ConstantFieldRefInfo,
    ConstantInfoTag.MethodRef: ConstantMethodRefInfo,
    ConstantInfoTag.InterfaceMethodRef: ConstantInterfaceMethodRefInfo,
    ConstantInfoTag.NameAndType: ConstantNameAndTypeInfo,
    ConstantInfoTag.String: ConstantStringInfo,
    ConstantInfoTag.InvokeDynamic: ConstantInvokeDynamicInfo,
    ConstantInfoTag.MethodType: ConstantMethodTypeInfo,
    ConstantInfoTag.Class: ConstantClassInfo,
    ConstantInfoTag.MethodHandle: ConstantMethodHandleInfo
}

__all__ = ['ConstantPool', 'ConstantUtf8Info', 'ConstantIntegerInfo', 'ConstantLongInfo',
           'ConstantFloatInfo', 'ConstantDoubleInfo', 'ConstantFieldRefInfo', 'ConstantMethodRefInfo',
           'ConstantInterfaceMethodRefInfo', 'ConstantNameAndTypeInfo', 'ConstantStringInfo',
           'ConstantInvokeDynamicInfo', 'ConstantMethodTypeInfo', 'ConstantClassInfo',
           'ConstantMethodHandleInfo', 'CONSTANT_INFO_MAP', 'T_ConstInfo', 'ConstantInfoTag',
           'BaseConstantRefInfo']
